﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using ICommonLibrary.Collection;

namespace ICommonLibrary {

    /// <summary>
    /// NOTE: <T> must be an Exception type with a constructor of a single string parameter.
    /// </summary>
    public class Asserts {
        
	    private Asserts() {}

        public static void True(bool expression) {
            True<ArgumentException>(expression, "[Assertion failed] - this expression must be true");
        }

        public static void True<T>(bool expression) where T : Exception {
            True<T>(expression, "[Assertion failed] - this expression must be true");
        }

        public static void True(bool expression, string message) {
            True<ArgumentException>(expression, message);
	    }

        public static void True<T>(bool expression, string message) where T : Exception {
            if (!expression)
                Fail<T>(message);
        }

        public static void False(bool expression) {
            False<ArgumentException>(expression, "[Assertion failed] - this expression must be false");
        }

        public static void False<T>(bool expression) where T : Exception {
            False<T>(expression, "[Assertion failed] - this expression must be false");
        }

	    public static void False(bool expression, string message) {
            False<ArgumentException>(expression, message);
	    }

        public static void False<T>(bool expression, string message) where T : Exception {
            if (expression)
                Fail<T>(message);
        }

        public new static void Equals(object expected, object actual) {
            Equals<ArgumentException>(expected, actual, null);
        }

        public static void Equals<T>(object expected, object actual) where T : Exception {
            Equals<T>(expected, actual, null);
        }

	    public static void Equals(object expected, object actual, string message) {
            Equals<ArgumentException>(expected, actual, message);
	    }

        public static void Equals<T>(object expected, object actual, string message) where T : Exception {
            if (expected == null && actual == null)
                return;
            if (expected != null && expected.Equals(actual))
                return;
            Fail<T>(Format(expected, actual, message));
        }

        public static void NotEquals(object expected, object actual) {
            NotEquals<ArgumentException>(expected, actual, null);
        }

        public static void NotEquals<T>(object expected, object actual) where T : Exception {
            NotEquals<T>(expected, actual, null);
        }

	    public static void NotEquals(object expected, object actual, string message) {
            NotEquals<ArgumentException>(expected, actual, message);
	    }

        public static void NotEquals<T>(object expected, object actual, string message) where T : Exception {
            // return if one is null, the other isn't.
            if (expected == null ^ actual == null) {
                return;
            }

            // either both are null, or neither is.
            if (expected != null && !expected.Equals(actual))
                return;
            Fail<T>(Format(expected, actual, message));
        }

	    public static void NotNull(object o) {
		    NotNull<ArgumentException>(o, null);
	    }

        public static void NotNull<T>(object o) where T : Exception {
            NotNull<T>(o, null);
        }

	    public static void NotNull(object o, string message) {
            NotNull<ArgumentException>(o, message);
	    }

        public static void NotNull<T>(object o, string message) where T : Exception {
            if (o == null)
                Fail<T>(message);
        }

	    public static void Null(object o) {
		    Null<ArgumentException>(o, null);
	    }

        public static void Null<T>(object o) where T : Exception {
            Null<T>(o, null);
        }

	    public static void Null(object o, string message) {
            Null<ArgumentException>(o, message);
	    }

        public static void Null<T>(object o, string message) where T : Exception {
            if (o != null)
                Fail<T>(message);
        }

        public static void Same(object expected, object actual) {
            Same<ArgumentException>(expected, actual, null);
        }

        public static void Same<T>(object expected, object actual) where T : Exception {
            Same<T>(expected, actual, null);
        }

        public static void Same(object expected, object actual, string message) {
            Same<ArgumentException>(expected, actual, message);
        }

        public static void Same<T>(object expected, object actual, string message) where T : Exception {
		    if (ReferenceEquals(expected, actual))
			    return;
            Fail<T>(Format(expected, actual, message));
	    }

        public static void NotSame(object expected, object actual) {
            NotSame<ArgumentException>(expected, actual, "expected not Same");
        }

        public static void NotSame<T>(object expected, object actual) where T : Exception {
            NotSame<T>(expected, actual, "expected not Same");
        }

        public static void NotSame(object expected, object actual, string message) {
            NotSame<ArgumentException>(expected, actual, message);
        }

        public static void NotSame<T>(object expected, object actual, string message) where T : Exception {
            if (expected == actual)
                Fail<T>(message);
        }

	    public static void NotBlank(string s) {
		    NotBlank<ArgumentException>(s, "[Assertion failed] - this expression must not be null nor empty");
	    }

        public static void NotBlank<T>(string s) where T : Exception {
            NotBlank<T>(s, "[Assertion failed] - this expression must not be null nor empty");
        }

        public static void NotBlank(string s, string message) {
            NotBlank<ArgumentException>(s, message);
	    }

        public static void NotBlank<T>(string s, string message) where T : Exception {
            if (string.IsNullOrEmpty(s))
                Fail<T>(message);
        }

	    public static void Blank(string s) {
            Blank<ArgumentException>(s, "[Assertion failed] - this expression must be blank");
	    }

        public static void Blank<T>(string s) where T : Exception {
            Blank<T>(s, "[Assertion failed] - this expression must be blank");
        }

	    public static void Blank(string s, string message) {
            Blank<ArgumentException>(s, message);
	    }

        public static void Blank<T>(string s, string message) where T : Exception {
            if (!string.IsNullOrEmpty(s))
                Fail<T>(message);
        }

        public static void NotEmpty<C>(C[] array) {
            NotEmpty<ArgumentException, C>(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
        }

        public static void NotEmpty<T, C>(C[] array) where T : Exception {
            NotEmpty<T, C>(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
        }
        
        public static void NotEmpty<C>(C[] array, string message) {
            NotEmpty<ArgumentException, C>(array, message);
	    }

        public static void NotEmpty<T>(object[] array, string message) where T : Exception {
            if (ArrayUtil.IsEmpty(array)) {
                Fail<T>(message);
            }
        }
        
        public static void NotEmpty<C>(ICollection<C> collection) {
            NotEmpty<ArgumentException, C>(collection, "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
        }
        
        public static void NotEmpty<T, C>(ICollection<C> collection) where T : Exception {
            NotEmpty<T, C>(collection, "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
        }

	    public static void NotEmpty<C>(ICollection<C> collection, string message) {
		    NotEmpty<ArgumentException, C>(collection, message);
	    }

        public static void NotEmpty<T, C>(ICollection<C> collection, string message) where T : Exception {
            if (CollectionUtil.IsEmpty(collection)) {
                Fail<T>(message);
            }
        }

        public static void DefinedEnum(Type enumType, object value, string message) {
            True(enumType.IsEnum, enumType.FullName + " is not enum");
            True(Enum.IsDefined(enumType, value), message);
        }

        public static void DefinedEnum<T>(Type enumType, object value, string message) where T : Exception {
            True(enumType.IsEnum, enumType.FullName + " is not enum");
            True<T>(Enum.IsDefined(enumType, value), message);
        }

        private static void Fail<T>(string message) where T : Exception {
            Type type = typeof (T);
            ConstructorInfo constructor = type.GetConstructor(new[] {typeof (string)});
            if (constructor == null) {
                throw new ArgumentException("Class[" + type.FullName + "] must have a constructor of a single string parameter");
            }
            throw (Exception) constructor.Invoke(new [] {message});
        }

        private static string Format(object expected, object actual, string message) {
            StringBuilder formatted = new StringBuilder();
            if (message != null)
                return message;

		    return  formatted.Append("expected :<").
                    Append(expected).
                    Append("> but was:<").
                    Append(actual).
                    Append(">").
                    ToString();
	    }
    }
}
